package org.ccay.security.reaml.impl;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Named;

import org.ccay.security.annotation.SecurityBindingURL;
import org.ccay.security.annotation.SecurityOperation;
import org.ccay.security.annotation.SecurityPolicy;
import org.ccay.security.annotation.SecurityResource;
import org.ccay.security.entity.Permission;
import org.ccay.security.reaml.IPermissionMetadataScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;

@Named
public class PermissionMetadataScanner implements IPermissionMetadataScanner,InitializingBean,ApplicationContextAware {
	
	private static final Logger log = LoggerFactory.getLogger(PermissionMetadataScanner.class);
	
	private String DEFAULT_BASEPACKAGE = "org.ccay";

	private String resourcePattern = "**/*.class";

	private List<String> basePackages;
	
	private ResourcePatternResolver resourcePatternResolver;

	private MetadataReaderFactory metadataReaderFactory;

	private final Map<TypeFilter, Class<? extends Annotation>> includeFilters = new HashMap<TypeFilter, Class<? extends Annotation>>();

	private final List<Class<? extends Annotation>> annotations = new ArrayList<Class<? extends Annotation>>();
	
	private Set<Permission> loadedPermissionCache = new HashSet<Permission>();
	
	public PermissionMetadataScanner(){
		annotations.add(SecurityResource.class);
		for (Class<? extends Annotation> ann : annotations) {
			includeFilters.put(new AnnotationTypeFilter(ann), ann);
		}
	}
	
	@Override
	public Set<Permission> loadPermissionsMetadatas() {
		if(loadedPermissionCache == null || loadedPermissionCache.isEmpty()){
			Set<Permission> permission = new HashSet<Permission>();
			for(String basePath:basePackages){
				scan(basePath,permission);
			}
			loadedPermissionCache = permission;
		}
		
		return loadedPermissionCache;
	}
		 
	
	private void scan(String basePackage,Set<Permission> permissions){
		String packageSearchPath = "classpath*:" + resolveBasePackage(basePackage) + "/" + this.resourcePattern;
		try {
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				if (resource.isReadable()) {
					MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
					for (TypeFilter tf : this.includeFilters.keySet()) {
						if (tf.match(metadataReader, this.metadataReaderFactory)) {
							AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
							permissions.addAll(parseMethodPermissions(annotationMetadata));
							try {
								permissions.addAll(parseFeildPermissions(annotationMetadata,Class.forName(metadataReader.getClassMetadata().getClassName())));
							} catch (ClassNotFoundException e) {
								log.warn("When parse Feild SecurityOperation MetaData : " + e.getMessage());
							}
						}
					}
				} else {
					log.warn("Ignored because not readable: " + resource);
				}

			}
		} catch (IOException e) {
			log.warn("When load MetaData : " + e.getMessage());
		}
	}
	
	/**
	 * 解析属性上标注的SecurityOperation
	 * @param annotationMetadata
	 * @param clazz
	 * @return
	 */
	private Set<Permission> parseFeildPermissions(AnnotationMetadata annotationMetadata,Class<?> clazz){
		Set<Permission> permissions = new HashSet<Permission>();
		Map<String, Object> resourceAttrs = annotationMetadata.getAnnotationAttributes(SecurityResource.class.getName());
		Field[] fields = clazz.getDeclaredFields();
		for(Field field : fields){
			if(field.isAnnotationPresent(SecurityOperation.class)){
				SecurityOperation sq =field.getAnnotation(SecurityOperation.class);
				Permission permission = new Permission(
						(String)resourceAttrs.get(SecurityResource.ATTR_CODE),
						(String)resourceAttrs.get(SecurityResource.ATTR_NAME),
						(String)resourceAttrs.get(SecurityResource.ATTR_DESC),
						sq.code(),
						sq.name(),
						sq.desc(),
						SecurityPolicy.Required
						);
				if(field.isAnnotationPresent(SecurityBindingURL.class)){
					permission.setBindingURL(field.getAnnotation(SecurityBindingURL.class).value());
				}
				permissions.add(permission);
			}
		}
		return permissions;
	}
	/**
	 * 解析方法上标注的SecurityOperation
	 * @param annotationMetadata
	 * @return
	 */
	private Set<Permission> parseMethodPermissions(AnnotationMetadata annotationMetadata){
		Set<Permission> permissions = new HashSet<Permission>();
		Map<String, Object> resourceAttrs = annotationMetadata.getAnnotationAttributes(SecurityResource.class.getName());
		
		Set<MethodMetadata> methodMetadate = annotationMetadata.getAnnotatedMethods(SecurityOperation.class.getName());
		for(MethodMetadata mm:methodMetadate){
			Map<String, Object> opreationAttrs = mm.getAnnotationAttributes(SecurityOperation.class.getName());
			Permission permission = new Permission(
					(String)resourceAttrs.get(SecurityResource.ATTR_CODE),
					(String)resourceAttrs.get(SecurityResource.ATTR_NAME),
					(String)resourceAttrs.get(SecurityResource.ATTR_DESC),
					(String)opreationAttrs.get(SecurityOperation.ATTR_CODE),
					(String)opreationAttrs.get(SecurityOperation.ATTR_NAME),
					(String)opreationAttrs.get(SecurityOperation.ATTR_DESC),
					(SecurityPolicy) opreationAttrs.get(SecurityOperation.ATTR_POLICY)
					);
			
			Map<String, Object> bindingURLAttrs = mm.getAnnotationAttributes(SecurityBindingURL.class.getName());
			if(bindingURLAttrs!=null && !bindingURLAttrs.isEmpty()){
				permission.setBindingURL((String)bindingURLAttrs.get("value"));
			}
			permissions.add(permission);
		}
		return permissions;
	}
	

	protected String resolveBasePackage(String basePackage) {
		return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
	}


	@Override
	public void afterPropertiesSet() throws Exception {
		if(null==basePackages || basePackages.isEmpty()){
			basePackages = new ArrayList<String>();
		}
		basePackages.add(DEFAULT_BASEPACKAGE);
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(applicationContext);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(applicationContext);
	}

	public void setBasePackages(List<String> basePackages) {
		this.basePackages = basePackages;
	}


}
